# coding: utf-8

from collections import defaultdict
from datetime import timedelta, datetime, date, time
import decimal
import json
import traceback
import urllib.request, urllib.error, urllib.parse
import time

from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import Manager
from django.http import JsonResponse, QueryDict
from django.utils.timezone import is_aware

from lib.lock import LocalLock
from services.datamodel.share import datamodel_logger

from aliyunsdkcore import client
from aliyunsdkcore.profile import region_provider
from aliyunsdkgreen.request.v20180509 import TextScanRequest
import uuid

now = datetime.now
now_timestamp = time.time


class AbstractHandler:
    def __init__(self, app_logger):
        self.app_logger = app_logger
        self.METHOD_TAB = None
        self.paras = None

    def dispatch(self, request, *args, **kwargs):
        # 先检查请求方式和请求参数的有效性
        valid_method = {"GET", "POST", "PUT", "DELETE"}
        if request.method not in valid_method.intersection(self.METHOD_TAB):
            return JsonResponse({"retcode": 1, "msg": "不支持的 method 类型"})
        elif request.method in ("GET", "POST"):
            _paras = getattr(request, request.method)
        else:
            _paras = QueryDict(request.body)

        # 检查传入参数合法性
        request.param_dict = _paras
        self.paras = {k: v for k, v in list(_paras.items())}
        dispatcher = self.METHOD_TAB[request.method]

        if hasattr(dispatcher, "__call__"):
            handler = dispatcher
        elif "action" not in _paras:
            return JsonResponse({"retcode": 1, "msg": "需要参数`action`"})
        elif _paras["action"] not in dispatcher:
            return JsonResponse({"retcode": 1, "msg": "不支持的`action`类型"})
        else:
            handler = dispatcher[_paras["action"]]

        # 请求方式和请求参数都没有问题后开始处理请求内容
        try:
            resp = handler(request, *args, **kwargs)
        except AssertionError as e:
            # resp = {"retcode": e.message[0], "msg": e.message[1]}
            # python3 AssertionError 没有message属性，e.args为tuple，0位存储错误信息
            resp = {"retcode": e.args[0][0], "msg": e.args[0][1]}
        except Exception as e:
            msg = traceback.format_exc()
            self.app_logger.error(e.args[0])
            self.app_logger.error(msg)
            resp = {"retcode": 1, "msg": msg}
        if isinstance(resp, dict):
            resp = JsonResponse(resp)
        return resp

    @staticmethod
    def checkMandatoryParams(request, params):
        msg = "缺少参数`%s`"
        for param in params:
            assert request.param_dict.get(param) is not None, (1, msg % param)

    @staticmethod
    def is_int_params(request, params):
        msg = "参数：`%s`类型必须是个非负整数"
        for param in params:
            one = request.param_dict.get(param, "0")
            assert one.isdigit(), (1, msg % param)

    @staticmethod
    def is_not_zero_int_params(request, params):
        msg = "参数：`%s`类型必须是个正整数"
        for param in params:
            one = request.param_dict.get(param, "1")
            assert one.isdigit() and one != "0", (1, msg % param)

    @staticmethod
    def is_json_params(request, params):
        msg = "参数：`%s`格式必须是列表或者字典"
        for param in params:
            one = request.param_dict.get(param, "[]")
            status = False
            try:
                json_one = json.loads(one)
                status = isinstance(json_one, (dict, list))
            except (ValueError, TypeError):
                pass
            assert status, (1, msg % param)

    @staticmethod
    def is_0_or_1_params(request, params):
        msg = "参数：`%s`从 [0, 1] 中选取"
        for param in params:
            one = request.param_dict.get(param, "0")
            assert one in ["0", "1"], (1, msg % param)

    @staticmethod
    def param_in_options(request, param, options):
        msg = "参数：`%s`从 %s 中选取"
        one = request.param_dict.get(param)
        if one is not None:
            assert one in options, (1, msg % (param, options))

    @staticmethod
    def param_in_json_options(param_name, param_obj, options):
        msg = "参数%s：缺少key为%s的值"

        for item in options:
            if item not in param_obj:
                assert False, (1, msg % (param_name, item))

    @staticmethod
    def param_any_in_json_options(param_name, param_obj, options):
        msg = "参数：`%s`需从 `%s` 中选取"

        if not options & param_obj.keys() or param_obj.keys() - options:
            assert False, (1, msg % (param_name, options))

    @staticmethod
    def param_any_options(request, params):
        """
        参数 A，B 必须存在其中一个, 最少有其中一个，可以全部存在
        """
        msg = "参数%s必须含有其中一个"
        pre_list = []
        for param in params:
            pre_list.append(request.param_dict.get(param))
        assert any(pre_list), (1, msg % params)

    @staticmethod
    def get_client_ip(request):
        x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
        if x_forwarded_for:
            ip = x_forwarded_for.split(',')[0]
        else:
            ip = request.META.get('REMOTE_ADDR')
        return ip

    @staticmethod
    def allowed_usertype(request, allowed_list):
        msg = "允许的用户类型为%s" % allowed_list
        ut = request.session.get('ut')
        if ut is not None:
            assert ut in allowed_list, (1, msg)

    @staticmethod
    def result(retcode=0, message='', data=None, kwargs=None):
        json_dict = {'data': data, 'retcode': retcode, 'message': message}
        if kwargs and isinstance(kwargs, dict) and kwargs.keys():
            json_dict.update(kwargs)
        return JsonResponse(json_dict)

    @staticmethod
    def success(data=None):
        return AbstractHandler.result(retcode=0, message='OK', data=data)

    @staticmethod
    def error(message='', data=None):
        return AbstractHandler.result(retcode=1, message=message, data=data)


class CacheKeys:
    student_uid2sid = "student_uid2sid_%s"  # 根据学生user id 获取 studentid, 缓存1天
    student_sid2uid = "student_sid2uid_%s"  # 根据学生studentid 获取user id , 缓存1天
    studentuid2parentuids = "child_{childuserid}_parentuidlist"  # 根据学生user id 获取家长user id , 缓存1天
    APP_VERSION = "APP_VERSION_%s_%s"  # app版本信息,占位符1为ostype, 0表示安卓,1表示苹果;
    course = 'course_%s'  # 某个课程基础信息
    chapter = 'chapter_%s'  # 某个章节详细信息
    LOGIN_ID = "login_%s"  # 用于存储登录用户session_key, cache_key信息， 缓存时间为Session缓存时间
    USER_BALANCE = "user_cards_num_%s_%s"  # 用户卡余额, 两个占位符分别为student表id和roleid
    USER_TOTAL_CARD = "cards_total_user_%s_%s"  # 用户累计购卡总数
    device_key = "device_{uid}"  # 根据user id devicekey , 缓存1天
    ACCESS_TOKEN_WECHAT = "wechat_key"  # 微信access_token 1小時50分過期
    JS_ACCESS_TOKEN_WECHAT = "js_wechat_key"  # 微信js_access_token 1小時50分過期
    DIRECT_DAILI_TOTALL_USER = "direct_daili_totall_user_%s"  # 代理-直接开户代理
    INDIRECT_DAILI_TOTALL_USER = "indirect_daili_totall_user_%s"  # 代理-简介开户代理
    VIP_TOTALL_USER = "vip_totall_user_%s"  # 代理vip会员总数
    VIP_FOR_NATURAL_FIOW_USER = "vip_for_natural_flow_user_%s"  # 代理-自然流量加入的vip数
    DIRECT_FOR_VIP_USER = "direct_for_vip_user_%s"  # 代理-直接发展vip数
    ORDER_MONEY_TOTAL = "order_money_total_%s"  # 代理销售总额(studentid)
    ORDER_TOTAL = "order_total_%s"  # 代理销售订单总数
    ORDER_MONTH_TOTAL = "order_month_total_%s_%s"  # 代理每月的销售订单总数
    MONEY_MONTH_TOTAL = "money_month_total_%s_%s"  # 代理每月的销售额度
    ORDER_YEAR_DETAIL = "order_year_detail_%s_%s"  # 代理每年每月的销售订单（两个占位符是studentid, 2021年份四位数）
    FROZEN_TOTAL = "frozen_total_%s"  # 冻结资金
    TOTAL_USER = "total_user"  # 总用户数
    MEMBER_VIP = "member_vip"  # 会员用户
    SHENG_AGENT = "sheng_agent"  # 省代理
    SHI_AGENT = "shi_agent"  # 市代理
    QU_AGENT = "qu_agent"  # 区代理
    SUPPLIER = "supplier"  # 供应商
    PLATINUM = "platinum"  # 铂金
    SPOKESMAN = "spokesman"  # 代言人
    PLATFORM_TOTAL_CARDS = "platform_total_cards"  # 平台总卖出卡数
    C_ACTIVATION = "c_activation"  # C端用户零售卡数
    PROXY_CERT = "proxy_cert_%s"  # 代理商证书地址(studentid)


# 签到获取的金币数
class CheckinCoins:
    PARENT = 1
    TEACHER = 10
    STUDENT = 1


class APPNUMLIMIT:
    PARENT_CHILD_QUESTION_CHART_LIMIT = 50
    PARENT_CHILD_SUBJECT_CORRECTRATIO_CHART_LIMIT = 7
    WEB_TASK_COUNT_PER_PAGE = 10
    APP_LIST_COUNT_PER_REFRESH = 20
    STUDENT_ACTIVITYTRACK_NUM = 30  # 家长拉取孩子轨迹数目


class COMMENTLIMIT:
    COMMENTPERPAGE = 20  # 评论个数
    REPLYPERPAGE = 20  # 回复个数


class NEWSLIMIT:
    NEWSPERPAGE = 10  # 文章个数


class TOPICLIMIT:
    TOPICQUESTIONSPERPAGE = 5  # 专题题目每页数量


# 称谓列表,家长注册时用到
# 约定: 99表示其他称谓
BUILTIN_STUDENT_PARENT_RELATION = (
    ("0", '爸爸'),
    ("1", '妈妈'),
    ("2", '爷爷'),
    ("3", '奶奶'),
    ("4", '外公'),
    ("5", '外婆'),
)

# 性别选择
GENDER_CHOICES = (
    ('M', 'Male'),
    ('F', 'Female'),
)

# 应急电话
EmergencyTelephone = [
    {"name": "报警电话", "number": 110, "relation": "应急"},
    {"name": "急救中心", "number": 120, "relation": "应急"},
    {"name": "火警", "number": 119, "relation": "应急"},
    {"name": "交通事故报警", "number": 122, "relation": "应急"},
    {"name": "中国移动", "number": 10086, "relation": "应急"},
    {"name": "中国联通", "number": 10010, "relation": "应急"},
    {"name": "中国电信", "number": 10000, "relation": "应急"},
]


class DjangoOverRideJSONEncoder(DjangoJSONEncoder):
    """
    JSONEncoder subclass that knows how to encode date/time and decimal types.
    """

    def default(self, o):
        # See "Date Time String Format" in the ECMA-262 specification.
        if isinstance(o, datetime):
            r = o.isoformat()
            # if o.microsecond:
            #     r = r[:23] + r[26:]
            if r.endswith('+00:00'):
                r = r[:-6] + 'Z'
            return r
        elif isinstance(o, date):
            return o.isoformat()
        elif isinstance(o, time):
            if is_aware(o):
                raise ValueError("JSON can't represent timezone-aware times.")
            r = o.isoformat()
            if o.microsecond:
                r = r[:12]
            return r
        elif isinstance(o, decimal.Decimal):
            return str(o)
        else:
            return super(DjangoJSONEncoder, self).default(o)


class SpecialReturnCode(object):
    PUBLIC_RETURN_ERROR_CODE = 1

    RESOURCE_NOT_EXIST = 10001  # 资源不存在code码，比如微课不存在
    RESOURCE_EXIST = 10002  # 资源已存在或已重复code码，比如创建老师分组重复
    NO_AUTHORIZED = 10003  # 没有权限操作
    PARAMETER_MISSING = 10004  # 缺失参数
    PARAMETER_WRONG = 10005  # 参数错误
    RESOURCE_IS_OWNER = 10006  # 資源是自己的
    RESOURCE_NOT_OWNER = 10007  # 資源不是自己的
    RESOURCE_GET_MULTI = 10008  # GET到多个实例
    DB_VALUE_WRONG = 10009  # 数据库内的数据格式错误
    DB_DELETE_WRONG = 10010  # 数据库删除错误

    PHONE_USED = 20001  # 手机已被占用
    STUDENTS_NUMBER_EXCEEDED = 20002  # 学生数超出
    INTEGRAL_OVER = 20003  # 积分达到上线
    TODAY_CHECK_IN = 20004  # 今日已经签到

    # 帐号相关ret code
    AccountNotExist = 30001
    AccountExist = 30002
    AccountErrorPassword = 30003


class BasicManager(Manager, SpecialReturnCode, CacheKeys):
    @staticmethod
    def loads_or_default(param, default=None):
        try:
            return json.loads(param)
        except (ValueError, TypeError):
            return default

    @staticmethod
    def dumps_or_default(param, default=None):
        try:
            return json.dumps(param)
        except TypeError:
            return default

    @staticmethod
    def dict_tree():
        t = lambda: defaultdict(t)
        return t()

    @staticmethod
    def default_now():
        return datetime.now()

    @staticmethod
    def default_start(seconds=60):
        return now() + timedelta(seconds=seconds)

    def lock(self, lock_key):
        mn = self.model._meta.model_name

        return LocalLock("{lock_%s}{%s}" % (mn, lock_key))

    def get_or_default(self, *args, **kwargs):
        is_raise = kwargs.pop("is_raise", False)
        msg_raise = kwargs.pop("msg_raise", "")
        log_level = kwargs.pop("log_level", False)
        log_msg = kwargs.pop("log_msg", "")
        raise_code = kwargs.pop("raise_code", None)
        default = kwargs.pop("default", None)
        select_related = kwargs.pop("select_related", [])

        if select_related:
            clone = self.filter(*args, **kwargs).select_related(*select_related)
        else:
            clone = self.filter(*args, **kwargs)
        if len(clone) == 1:
            return clone[0]

        if log_level:
            datamodel_logger.log(log_level, log_msg)
        if not is_raise:
            return default

        model_name = self.model._meta.model_name
        if raise_code is not None:
            ret_code = raise_code
        elif clone:
            ret_code = self.RESOURCE_GET_MULTI
        else:
            ret_code = self.RESOURCE_NOT_EXIST
        if msg_raise is not "":
            message = msg_raise
        elif clone:
            message = "多个实例：{} Model".format(model_name)
        else:
            message = "实例不存在：{} Model".format(model_name)
        assert False, (ret_code, message)


# . ' " + _ ?
def escape_string(raw):
    return raw.replace("+", "\\+").replace('?', '\\?').replace(
        '_', '\\_').replace('.', '\\.')


def caculate_rank(src_list, sorted_by):
    """
    :param src_list: 数据源，类型为list[dict,dict...]
    :param sorted_by: 按照dict中的key来排名
    :return: 返回类型为list[dict,dict...]
    """
    ordered_list = []
    grouped_dict = defaultdict(list)
    [grouped_dict[o[sorted_by]].append(o) for o in src_list]
    ordered_keys = sorted(list(grouped_dict.keys()), reverse=True)  # 降序列
    index = 1
    for key in ordered_keys:
        for d in grouped_dict[key]:
            d.update({'rank': index})
            ordered_list.append(d)
        index += len(grouped_dict[key])
    return ordered_list


def get_api_result(url_str):
    try:
        proxy_support = urllib.request.ProxyHandler({})
        opener = urllib.request.build_opener(proxy_support)
        urllib.request.install_opener(opener)
        req = urllib.request.Request(url_str)
        res_data = urllib.request.urlopen(req, timeout=8)
        res = res_data.read()
        res = json.loads(res)
        return res
    except Exception as e:
        datamodel_logger.log(40, str(e))
        return {}


def split_by_num(src_list, num):
    """
    将list平均分成num等份
    :param src_list: [1,2,3,4,5,6,7,8,9,10]
    :param num: 3
    :return: [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
    """

    def split_list(s_list, n):
        for i in range(0, len(s_list), n):
            yield src_list[i:i + n]

    return list(split_list(src_list, num))


def al_text(content):
    # 请使用您的AccessKey信息，支持修改配置文件或直接明文替换。
    clt = client.AcsClient("LTAI5t854B86EoG4NRb9xSZf", "SnafLoBuliLs7fMR2FhmkTOBBHuKZH", 'cn-shanghai')
    region_provider.modify_point('Green', 'cn-shanghai', 'green.cn-shanghai.aliyuncs.com')
    # 每次请求时需要新建request，请勿复用request对象。
    request = TextScanRequest.TextScanRequest()
    request.set_accept_format('JSON')
    task1 = {"dataId": str(uuid.uuid1()),
             "content": content,
             "time": datetime.now().microsecond
             }
    # 文本反垃圾检测场景的场景参数是antispam。
    request.set_content(bytearray(json.dumps({"tasks": [task1], "scenes": ["antispam"]}), "utf-8"))
    response = clt.do_action_with_exception(request)
    # print(response)
    result = json.loads(response)
    if 200 == result["code"]:
        taskResults = result["data"]
        for taskResult in taskResults:
            if (200 == taskResult["code"]):
                 sceneResults = taskResult["results"]
                 for sceneResult in sceneResults:
                     scene = sceneResult["scene"]
                     suggestion = sceneResult["suggestion"]
                     # 根据scene和suggetion设置后续操作。
                     if suggestion == "block":
                         return suggestion
        return "ok"

# 日志action中文字典
LOG_ACTION_CHINESE = {
    'yj_questiontags': {
        'name': '题目标签',
        'add': '增加',
        'modify': '修改信息',
        'reorder': '记录展示排序',
    },
    'yj_examtags': {
        'name': '试卷标签',
        'add': '增加',
        'modify': '修改信息',
        'reorder': '记录展示排序',
    },
    'yj_lessontags': {
        'name': '微课标签',
        'add': '增加',
        'modify': '修改信息',
        'reorder': '记录展示排序',
    },
    'yj_questions': {
        'name': '题目',
        'add': '增加',
        'modify_prepared_question': '待录入信息修改',
        'add_question_to_topic': '添加到专题题目',
        'modify': '修改信息'
    },
    'yj_exams': {
        'name': '试卷',
        'add': '增加',
        'modify': '修改信息',
    },
    'yj_lessons': {
        'name': '微课',
        'add': '增加',
        'modify': '修改信息',
        'setasdefault': 'setasdefault',
        'set_lesson_shared_status': '设置共享状态',
    },
    'news': {
        'name': '资讯',
        'create': '增加',
        'modify': '上/下架',
        'post': '修改信息',
        'modify_category': '修改标签',
        'sort_category': '设置标签',
    },
    'push': {
        'name': '推送',
        'create': '新增',
        'modify': '修改',
        'delete': '删除',
        'cancel': '取消',
        'recover': '恢复',
    },
    'yj_special_topic': {
        'name': '专题',
        'add': '新增',
        'add_topic_structs': '设置目录',
        'add_question_to_topic': '添加题目',
    },
    'yj_booktext': {
        'name': '英语课文',
        'add': '增加',
        'modify': '修改信息',
    },
    'mam': {
        'name': 'mam',
        'create_pic': '增加一张桌面图片',
        'modify_pic': '修改一张桌面图片',
        'set_one_pic_current': '设置主桌面背景的图片',
        'delete_pic': '删除一张桌面图片',

        'create_pad': '增加一台PAD',
        'modify_pad': '修改一台PAD相关属性',
        'delete_pad': '删除一台PAD',

        'create_app': '增加一款APP',
        'modify_app': '修改一款APP相关属性',
        'delete_app': '删除一款APP',
        'update_app_version': '更新一款APP版本',

        'create_adv': '增加一条ADV',
        'modify_adv': '修改一条ADV相关属性',
        'delete_adv': '删除一条ADV',

        'notice_install_app': '通知指定用户安装应用',
        'notice_update_app': '通知指定用户更新应用',
        'notice_remove_app': '通知指定用户卸载应用',

        'add_pad_root': '增加用户pad的root权限',
        'cancel_pad_root': '取消用户pad的root权限',
    }
}


def single_get_first(unicode1):
    str1 = unicode1.encode('gbk')
    try:
        ord(str1)
        return unicode1.upper()
    except:
        asc = str1[0] * 256 + str1[1] - 65536
        if -20319 <= asc <= -20284:
            return 'A'
        if -20283 <= asc <= -19776:
            return 'B'
        if -19775 <= asc <= -19219:
            return 'C'
        if -19218 <= asc <= -18711:
            return 'D'
        if -18710 <= asc <= -18527:
            return 'E'
        if -18526 <= asc <= -18240:
            return 'F'
        if -18239 <= asc <= -17923:
            return 'G'
        if -17922 <= asc <= -17418:
            return 'H'
        if -17417 <= asc <= -16475:
            return 'J'
        if -16474 <= asc <= -16213:
            return 'K'
        if -16212 <= asc <= -15641:
            return 'L'
        if -15640 <= asc <= -15166:
            return 'M'
        if -15165 <= asc <= -14923:
            return 'N'
        if -14922 <= asc <= -14915:
            return 'O'
        if -14914 <= asc <= -14631:
            return 'P'
        if -14630 <= asc <= -14150:
            return 'Q'
        if -14149 <= asc <= -14091:
            return 'R'
        if -14090 <= asc <= -13119:
            return 'S'
        if -13118 <= asc <= -12839:
            return 'T'
        if -12838 <= asc <= -12557:
            return 'U'
        if -12556 <= asc <= -11848:
            return 'X'
        if -11847 <= asc <= -11056:
            return 'Y'
        if -11055 <= asc <= -10247:
            return 'Z'
        return ''


def getFirstPinyin(string):
    if string is None:
        return None
    lst = list(string)
    return single_get_first(lst[0])


def getCurrentGrade(timestamp=True):
    if timestamp:
        curr_time = now()
        if curr_time < datetime(curr_time.year, 9, 1):
            grade_start_time = datetime(curr_time.year - 1, 9, 1)
        else:
            grade_start_time = datetime(curr_time.year, 9, 1)
        return int(grade_start_time.timestamp()), int(curr_time.timestamp())


# 成单课程
BILL_COURSE = {
    3: "写作课训练营（2980）",
    4: "定位小课班（4999）",
    5: "优势训练营单营（1080）",
    6: "优势训练营两营（1880）",
    7: "优势训练营三营（2780）"
}
# 成交价格(单位/分)
BILL_CHARGE = {
    3: 298000,
    4: 499900,
    5: 108000,
    6: 188000,
    7: 278000
}

# 公海线索 channel值
UNCLAIMED = {
    6 : "导入用户",
    9 : "抖音公海"
}
